/**
 * Copyright  2025, Hai Yue Xing He  ZHAO LIMIN
 *
 * @author        ZHAO LIMIN
 * @version       1.0.1
 * @since         2025-04
 * Rectangle.ts     矩形类
 * 一个矩形类，用于表示一个矩形区域
 * 包含一些常用的操作，如判断是否包含某个点，是否相交等
 * 同时也提供了一些静态方法，用于计算矩形的一些属性，如宽度，高度，中心点等
 */
import { Cartesian3 } from './Cartesian3' 
import { Check } from './Check'
import { defined } from './Defined' 
import { HYXHMathUtil } from './HYXHMathUtil'
import { defaultValue } from './HYXHDefaultValue'
import { UNION_NUMBER_ARRAY } from './HDataType'
export interface IRectangle2D {
  west: number
  south: number
  east: number
  north: number
  x: number
  y: number
  width: number
  height: number
}

export class Rectangle implements IRectangle2D {
  public west: number //The westernmost longitude, in radians, in the range [-Pi, Pi].
  public south: number
  public east: number
  public north: number
  public x: number
  public y: number
  public width: number
  public height: number
  /**
   * 以经度和纬度坐标指定的二维区域。
   *
   * @alias Rectangle
   * @constructor
   *
   * @param {number} [west=0.0] The westernmost longitude, in radians, in the range [-Pi, Pi].
   * @param {number} [south=0.0] The southernmost latitude, in radians, in the range [-Pi/2, Pi/2].
   * @param {number} [east=0.0] The easternmost longitude, in radians, in the range [-Pi, Pi].
   * @param {number} [north=0.0] The northernmost latitude, in radians, in the range [-Pi/2, Pi/2].
   *
   * @see Packable
   */
  constructor(west?: number, south?: number, east?: number, north?: number) {
    /**
     * The westernmost longitude in radians in the range [-Pi, Pi].
     *
     * @type {number}
     * @default 0.0
     */
    this.west = west ?? 0.0

    /**
     * The southernmost latitude in radians in the range [-Pi/2, Pi/2].
     *
     * @type {number}
     * @default 0.0
     */
    this.south = south ?? 0.0

    /**
     * The easternmost longitude in radians in the range [-Pi, Pi].
     *
     * @type {number}
     * @default 0.0
     */
    this.east = east ?? 0.0

    /**
     * The northernmost latitude in radians in the range [-Pi/2, Pi/2].
     *
     * @type {number}
     * @default 0.0
     */
    this.north = north ?? 0.0

    this.x = 0.0
    this.y = 0.0
    this.width = Rectangle.computeWidth(this)
    this.height = Rectangle.computeHeight(this)
  }
  /**
   * The number of elements used to pack the object into an array.
   * @type {number}
   */
  public static readonly packedLength = 4
  /**
   * Stores the provided instance into the provided array.
   *
   * @param {Rectangle} value The value to pack.
   * @param {number[]} array The array to pack into.
   * @param {number} [startingIndex=0] The index into the array at which to start packing the elements.
   *
   * @returns {number[]} The array that was packed into
   */
  public static pack(
    value: Rectangle,
    array: UNION_NUMBER_ARRAY,
    startingIndex: number = 0,
  ): UNION_NUMBER_ARRAY {
    //>>includeStart('debug', pragmas.debug);
    Check.typeOf.object('value', value)
    Check.defined('array', array)
    //>>includeEnd('debug');

    startingIndex = startingIndex ?? 0

    array[startingIndex++] = value.west
    array[startingIndex++] = value.south
    array[startingIndex++] = value.east
    array[startingIndex] = value.north

    return array
  }
  /**
   * Retrieves an instance from a packed array.
   *
   * @param {number[]} array The packed array.
   * @param {number} [startingIndex=0] The starting index of the element to be unpacked.
   * @param {Rectangle} [result] The object into which to store the result.
   * @returns {Rectangle} The modified result parameter or a new Rectangle instance if one was not provided.
   */
  public static unpack(array: UNION_NUMBER_ARRAY, startingIndex: number, result: Rectangle): Rectangle {
    //>>includeStart('debug', pragmas.debug);
    Check.defined('array', array)
    //>>includeEnd('debug');
    startingIndex = startingIndex ?? 0
    if (!defined(result)) {
      result = new Rectangle()
    }

    result.west = array[startingIndex++]
    result.south = array[startingIndex++]
    result.east = array[startingIndex++]
    result.north = array[startingIndex]
    return result
  }
  /**
   * Computes the width of a rectangle in radians.
   * @param {Rectangle} rectangle The rectangle to compute the width of.
   * @returns {number} The width.
   */
  public static computeWidth(rectangle: Rectangle): number {
    //>>includeStart('debug', pragmas.debug);
    Check.typeOf.object('rectangle', rectangle)
    //>>includeEnd('debug');
    let east = rectangle.east
    const west = rectangle.west
    if (east < west) {
      east += HYXHMathUtil.TWO_PI
    }
    return east - west
  }
  /**
   * Computes the height of a rectangle in radians.
   * @param {Rectangle} rectangle The rectangle to compute the height of.
   * @returns {number} The height.
   */
  public static computeHeight(rectangle: Rectangle): number {
    //>>includeStart('debug', pragmas.debug);
    Check.typeOf.object('rectangle', rectangle)
    //>>includeEnd('debug');
    return rectangle.north - rectangle.south
  }
  /**
   * Creates a rectangle given the boundary longitude and latitude in degrees.
   *
   * @param {number} [west=0.0] The westernmost longitude in degrees in the range [-180.0, 180.0].
   * @param {number} [south=0.0] The southernmost latitude in degrees in the range [-90.0, 90.0].
   * @param {number} [east=0.0] The easternmost longitude in degrees in the range [-180.0, 180.0].
   * @param {number} [north=0.0] The northernmost latitude in degrees in the range [-90.0, 90.0].
   * @param {Rectangle} [result] The object onto which to store the result, or undefined if a new instance should be created.
   * @returns {Rectangle} The modified result parameter or a new Rectangle instance if none was provided.
   *
   * @example
   * const rectangle = Rectangle.fromDegrees(0.0, 20.0, 10.0, 30.0);
   */
  public static fromDegrees(
    west: number,
    south: number,
    east: number,
    north: number,
    result: Rectangle,
  ): Rectangle {
    west = HYXHMathUtil.toRadians(west ?? 0.0)
    south = HYXHMathUtil.toRadians(south ?? 0.0)
    east = HYXHMathUtil.toRadians(east ?? 0.0)
    north = HYXHMathUtil.toRadians(north ?? 0.0)

    if (!defined(result)) {
      return new Rectangle(west, south, east, north)
    }

    result.west = west
    result.south = south
    result.east = east
    result.north = north

    return result
  }
  /**
   * Creates a rectangle given the boundary longitude and latitude in radians.
   *
   * @param {number} [west=0.0] The westernmost longitude in radians in the range [-Math.PI, Math.PI].
   * @param {number} [south=0.0] The southernmost latitude in radians in the range [-Math.PI/2, Math.PI/2].
   * @param {number} [east=0.0] The easternmost longitude in radians in the range [-Math.PI, Math.PI].
   * @param {number} [north=0.0] The northernmost latitude in radians in the range [-Math.PI/2, Math.PI/2].
   * @param {Rectangle} [result] The object onto which to store the result, or undefined if a new instance should be created.
   * @returns {Rectangle} The modified result parameter or a new Rectangle instance if none was provided.
   *
   * @example
   * const rectangle = public static fromRadians(0.0, Math.PI/4, Math.PI/8, 3*Math.PI/4);
   */
  public static fromRadians(
    west: number,
    south: number,
    east: number,
    north: number,
    result: Rectangle,
  ): Rectangle {
    if (!defined(result)) {
      return new Rectangle(west, south, east, north)
    }
    result.west = west ?? 0.0
    result.south = south ?? 0.0
    result.east = east ?? 0.0
    result.north = north ?? 0.0
    return result
  }
  
  /**
   * Duplicates a Rectangle.
   *
   * @param {Rectangle} rectangle The rectangle to clone.
   * @param {Rectangle} [result] The object onto which to store the result, or undefined if a new instance should be created.
   * @returns {Rectangle} The modified result parameter or a new Rectangle instance if none was provided. (Returns undefined if rectangle is undefined)
   */
  public static clone(rectangle: Rectangle, result?: Rectangle): Rectangle | undefined {
    if (!defined(rectangle)) {
      return undefined
    }

    if (!defined(result)) {
      return new Rectangle(rectangle.west, rectangle.south, rectangle.east, rectangle.north)
    }

    result!.west = rectangle.west
    result!.south = rectangle.south
    result!.east = rectangle.east
    result!.north = rectangle.north
    return result
  }
  /**
   * Compares the provided Rectangles componentwise and returns
   * <code>true</code> if they pass an absolute or relative tolerance test,
   * <code>false</code> otherwise.
   *
   * @param {Rectangle} [left] The first Rectangle.
   * @param {Rectangle} [right] The second Rectangle.
   * @param {number} [absoluteEpsilon=0] The absolute epsilon tolerance to use for equality testing.
   * @returns {boolean} <code>true</code> if left and right are within the provided epsilon, <code>false</code> otherwise.
   */
  public static equalsEpsilon(
    left: Rectangle,
    right: Rectangle,
    absoluteEpsilon: number = 0,
  ): boolean {
    absoluteEpsilon = absoluteEpsilon ?? 0
    return (
      left === right ||
      (defined(left) &&
        defined(right) &&
        Math.abs(left.west - right.west) <= absoluteEpsilon &&
        Math.abs(left.south - right.south) <= absoluteEpsilon &&
        Math.abs(left.east - right.east) <= absoluteEpsilon &&
        Math.abs(left.north - right.north) <= absoluteEpsilon)
    )
  }
  /**
   * Duplicates this Rectangle.
   *
   * @param {Rectangle} [result] The object onto which to store the result.
   * @returns {Rectangle} The modified result parameter or a new Rectangle instance if none was provided.
   */
  public clone(result: Rectangle): Rectangle | undefined {
    return Rectangle.clone(this, result)
  }
  /**
   * Compares the provided Rectangle with this Rectangle componentwise and returns
   * <code>true</code> if they are equal, <code>false</code> otherwise.
   *
   * @param {Rectangle} [other] The Rectangle to compare.
   * @returns {boolean} <code>true</code> if the Rectangles are equal, <code>false</code> otherwise.
   */
  public equals(other: Rectangle): boolean {
    return Rectangle.equals(this, other)
  }

  /**
   * Compares the provided rectangles and returns <code>true</code> if they are equal,
   * <code>false</code> otherwise.
   *
   * @param {Rectangle} [left] The first Rectangle.
   * @param {Rectangle} [right] The second Rectangle.
   * @returns {boolean} <code>true</code> if left and right are equal; otherwise <code>false</code>.
   */
  public static equals(left: Rectangle, right: Rectangle): boolean {
    return (
      left === right ||
      (defined(left) &&
        defined(right) &&
        left.west === right.west &&
        left.south === right.south &&
        left.east === right.east &&
        left.north === right.north)
    )
  }
  /**
   * Compares the provided Rectangle with this Rectangle componentwise and returns
   * <code>true</code> if they are within the provided epsilon,
   * <code>false</code> otherwise.
   *
   * @param {Rectangle} [other] The Rectangle to compare.
   * @param {number} [epsilon=0] The epsilon to use for equality testing.
   * @returns {boolean} <code>true</code> if the Rectangles are within the provided epsilon, <code>false</code> otherwise.
   */
  public equalsEpsilon(other: Rectangle, epsilon: number = 0): boolean {
    epsilon = epsilon ?? 0
    return Rectangle.equalsEpsilon(this, other, epsilon)
  }
  /**
   * Checks a Rectangle's properties and throws if they are not in valid ranges.
   *
   * @param {Rectangle} rectangle The rectangle to validate
   *
   * @exception {DeveloperError} <code>north</code> must be in the interval [<code>-Pi/2</code>, <code>Pi/2</code>].
   * @exception {DeveloperError} <code>south</code> must be in the interval [<code>-Pi/2</code>, <code>Pi/2</code>].
   * @exception {DeveloperError} <code>east</code> must be in the interval [<code>-Pi</code>, <code>Pi</code>].
   * @exception {DeveloperError} <code>west</code> must be in the interval [<code>-Pi</code>, <code>Pi</code>].
   * @private
   */
  public static _validate(rectangle: Rectangle) {
    //>>includeStart('debug', pragmas.debug);
    Check.typeOf.object('rectangle', rectangle)

    const north = rectangle.north
    Check.typeOf.tnumber.greaterThanOrEquals('north', north, -HYXHMathUtil.PI_OVER_TWO)
    Check.typeOf.tnumber.lessThanOrEquals('north', north, HYXHMathUtil.PI_OVER_TWO)

    const south = rectangle.south
    Check.typeOf.tnumber.greaterThanOrEquals('south', south, -HYXHMathUtil.PI_OVER_TWO)
    Check.typeOf.tnumber.lessThanOrEquals('south', south, HYXHMathUtil.PI_OVER_TWO)

    const west = rectangle.west
    Check.typeOf.tnumber.greaterThanOrEquals('west', west, -Math.PI)
    Check.typeOf.tnumber.lessThanOrEquals('west', west, Math.PI)

    const east = rectangle.east
    Check.typeOf.tnumber.greaterThanOrEquals('east', east, -Math.PI)
    Check.typeOf.tnumber.lessThanOrEquals('east', east, Math.PI)
    //>>includeEnd('debug');
  }
  
  /**
   * Computes the intersection of two rectangles.  This function assumes that the rectangle's coordinates are
   * latitude and longitude in radians and produces a correct intersection, taking into account the fact that
   * the same angle can be represented with multiple values as well as the wrapping of longitude at the
   * anti-meridian.  For a simple intersection that ignores these factors and can be used with projected
   * coordinates, see {@link Rectangle.simpleIntersection}.
   *
   * @param {Rectangle} rectangle On rectangle to find an intersection
   * @param {Rectangle} otherRectangle Another rectangle to find an intersection
   * @param {Rectangle} [result] The object onto which to store the result.
   * @returns {Rectangle|undefined} The modified result parameter, a new Rectangle instance if none was provided or undefined if there is no intersection.
   */
  public static intersection(
    rectangle: Rectangle,
    otherRectangle: Rectangle,
    result?: Rectangle,
  ): Rectangle | undefined {
    //>>includeStart('debug', pragmas.debug);
    Check.typeOf.object('rectangle', rectangle)
    Check.typeOf.object('otherRectangle', otherRectangle)
    //>>includeEnd('debug');

    let rectangleEast = rectangle.east
    let rectangleWest = rectangle.west

    let otherRectangleEast = otherRectangle.east
    let otherRectangleWest = otherRectangle.west

    if (rectangleEast < rectangleWest && otherRectangleEast > 0.0) {
      rectangleEast += HYXHMathUtil.TWO_PI
    } else if (otherRectangleEast < otherRectangleWest && rectangleEast > 0.0) {
      otherRectangleEast += HYXHMathUtil.TWO_PI
    }

    if (rectangleEast < rectangleWest && otherRectangleWest < 0.0) {
      otherRectangleWest += HYXHMathUtil.TWO_PI
    } else if (otherRectangleEast < otherRectangleWest && rectangleWest < 0.0) {
      rectangleWest += HYXHMathUtil.TWO_PI
    }

    const west = HYXHMathUtil.negativePiToPi(Math.max(rectangleWest, otherRectangleWest))
    const east = HYXHMathUtil.negativePiToPi(Math.min(rectangleEast, otherRectangleEast))

    if (
      (rectangle.west < rectangle.east || otherRectangle.west < otherRectangle.east) &&
      east <= west
    ) {
      return undefined
    }

    const south = Math.max(rectangle.south, otherRectangle.south)
    const north = Math.min(rectangle.north, otherRectangle.north)

    if (south >= north) {
      return undefined
    }

    if (!defined(result)) {
      return new Rectangle(west, south, east, north)
    }
    result!.west = west
    result!.south = south
    result!.east = east
    result!.north = north
    return result
  }
  /**
   * Computes a simple intersection of two rectangles.  Unlike {@link Rectangle.intersection}, this function
   * does not attempt to put the angular coordinates into a consistent range or to account for crossing the
   * anti-meridian.  As such, it can be used for rectangles where the coordinates are not simply latitude
   * and longitude (i.e. projected coordinates).
   *
   * @param {Rectangle} rectangle On rectangle to find an intersection
   * @param {Rectangle} otherRectangle Another rectangle to find an intersection
   * @param {Rectangle} [result] The object onto which to store the result.
   * @returns {Rectangle|undefined} The modified result parameter, a new Rectangle instance if none was provided or undefined if there is no intersection.
   */
  public static simpleIntersection(
    rectangle: Rectangle,
    otherRectangle: Rectangle,
    result?: Rectangle,
  ): Rectangle | undefined {
    //>>includeStart('debug', pragmas.debug);
    Check.typeOf.object('rectangle', rectangle)
    Check.typeOf.object('otherRectangle', otherRectangle)
    //>>includeEnd('debug');

    const west = Math.max(rectangle.west, otherRectangle.west)
    const south = Math.max(rectangle.south, otherRectangle.south)
    const east = Math.min(rectangle.east, otherRectangle.east)
    const north = Math.min(rectangle.north, otherRectangle.north)

    if (south >= north || west >= east) {
      return undefined
    }

    if (!defined(result)) {
      return new Rectangle(west, south, east, north)
    }

    result!.west = west
    result!.south = south
    result!.east = east
    result!.north = north
    return result
  }
  /**
   * Computes a rectangle that is the union of two rectangles.
   *
   * @param {Rectangle} rectangle A rectangle to enclose in rectangle.
   * @param {Rectangle} otherRectangle A rectangle to enclose in a rectangle.
   * @param {Rectangle} [result] The object onto which to store the result.
   * @returns {Rectangle} The modified result parameter or a new Rectangle instance if none was provided.
   */
  public static union(
    rectangle: Rectangle,
    otherRectangle: Rectangle,
    result?: Rectangle,
  ): Rectangle | undefined {
    //>>includeStart('debug', pragmas.debug);
    Check.typeOf.object('rectangle', rectangle)
    Check.typeOf.object('otherRectangle', otherRectangle)
    //>>includeEnd('debug');

    if (!defined(result)) {
      result = new Rectangle()
    }

    let rectangleEast = rectangle.east
    let rectangleWest = rectangle.west

    let otherRectangleEast = otherRectangle.east
    let otherRectangleWest = otherRectangle.west

    if (rectangleEast < rectangleWest && otherRectangleEast > 0.0) {
      rectangleEast += HYXHMathUtil.TWO_PI
    } else if (otherRectangleEast < otherRectangleWest && rectangleEast > 0.0) {
      otherRectangleEast += HYXHMathUtil.TWO_PI
    }

    if (rectangleEast < rectangleWest && otherRectangleWest < 0.0) {
      otherRectangleWest += HYXHMathUtil.TWO_PI
    } else if (otherRectangleEast < otherRectangleWest && rectangleWest < 0.0) {
      rectangleWest += HYXHMathUtil.TWO_PI
    }

    const west = HYXHMathUtil.negativePiToPi(Math.min(rectangleWest, otherRectangleWest))
    const east = HYXHMathUtil.negativePiToPi(Math.max(rectangleEast, otherRectangleEast))

    result!.west = west
    result!.south = Math.min(rectangle.south, otherRectangle.south)
    result!.east = east
    result!.north = Math.max(rectangle.north, otherRectangle.north)

    return result
  }
  
  /**
   * Computes a subsection of a rectangle from normalized coordinates in the range [0.0, 1.0].
   *
   * @param {Rectangle} rectangle The rectangle to subsection.
   * @param {number} westLerp The west interpolation factor in the range [0.0, 1.0]. Must be less than or equal to eastLerp.
   * @param {number} southLerp The south interpolation factor in the range [0.0, 1.0]. Must be less than or equal to northLerp.
   * @param {number} eastLerp The east interpolation factor in the range [0.0, 1.0]. Must be greater than or equal to westLerp.
   * @param {number} northLerp The north interpolation factor in the range [0.0, 1.0]. Must be greater than or equal to southLerp.
   * @param {Rectangle} [result] The object onto which to store the result.
   * @returns {Rectangle} The modified result parameter or a new Rectangle instance if none was provided.
   */
  public static subsection(
    rectangle: Rectangle,
    westLerp: number,
    southLerp: number,
    eastLerp: number,
    northLerp: number,
    result: Rectangle,
  ): Rectangle {
    //>>includeStart('debug', pragmas.debug);
    Check.typeOf.object('rectangle', rectangle)
    Check.typeOf.tnumber.greaterThanOrEquals('westLerp', westLerp, 0.0)
    Check.typeOf.tnumber.lessThanOrEquals('westLerp', westLerp, 1.0)
    Check.typeOf.tnumber.greaterThanOrEquals('southLerp', southLerp, 0.0)
    Check.typeOf.tnumber.lessThanOrEquals('southLerp', southLerp, 1.0)
    Check.typeOf.tnumber.greaterThanOrEquals('eastLerp', eastLerp, 0.0)
    Check.typeOf.tnumber.lessThanOrEquals('eastLerp', eastLerp, 1.0)
    Check.typeOf.tnumber.greaterThanOrEquals('northLerp', northLerp, 0.0)
    Check.typeOf.tnumber.lessThanOrEquals('northLerp', northLerp, 1.0)

    Check.typeOf.tnumber.lessThanOrEquals('westLerp', westLerp, eastLerp)
    Check.typeOf.tnumber.lessThanOrEquals('southLerp', southLerp, northLerp)
    //>>includeEnd('debug');

    if (!defined(result)) {
      result = new Rectangle()
    }

    // This function doesn't use HYXHMathUtil.lerp because it has floating point precision problems
    // when the start and end values are the same but the t changes.

    if (rectangle.west <= rectangle.east) {
      const width = rectangle.east - rectangle.west
      result.west = rectangle.west + westLerp * width
      result.east = rectangle.west + eastLerp * width
    } else {
      const width = HYXHMathUtil.TWO_PI + rectangle.east - rectangle.west
      result.west = HYXHMathUtil.negativePiToPi(rectangle.west + westLerp * width)
      result.east = HYXHMathUtil.negativePiToPi(rectangle.west + eastLerp * width)
    }
    const height = rectangle.north - rectangle.south
    result.south = rectangle.south + southLerp * height
    result.north = rectangle.south + northLerp * height

    // Fix floating point precision problems when t = 1
    if (westLerp === 1.0) {
      result.west = rectangle.east
    }
    if (eastLerp === 1.0) {
      result.east = rectangle.east
    }
    if (southLerp === 1.0) {
      result.south = rectangle.north
    }
    if (northLerp === 1.0) {
      result.north = rectangle.north
    }

    return result
  }

  /**
   * The largest possible rectangle.
   *
   * @type {Rectangle}
   * @constant
   */
  public static MAX_VALUE = Object.freeze(
    new Rectangle(-Math.PI, -HYXHMathUtil.PI_OVER_TWO, Math.PI, HYXHMathUtil.PI_OVER_TWO),
  )
} // end class Rectangle
const fromBoundingSpherePositionsScratch = new Array(5)
for (let n = 0; n < fromBoundingSpherePositionsScratch.length; ++n) {
  fromBoundingSpherePositionsScratch[n] = new Cartesian3()
} 
